package net.siufung.security;

import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.siufung.core.result.ResultFail;
import net.siufung.security.base.exception.Oauth2Exception;
import net.siufung.security.base.exception.catetory.InvalidUnknownException;
import net.siufung.security.base.exception.catetory.UsernameOrPasswordErrorException;
import net.siufung.security.base.service.ICurrentClientService;
import net.siufung.security.base.service.ICurrentUserService;
import net.siufung.security.granter.ITokenGranterProvider;
import net.siufung.security.provider.exception.AccessTokenAuthenticationException;
import net.siufung.security.provider.exception.CaptchaAuthenticationException;
import net.siufung.security.provider.exception.PasswdAuthenticationException;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.util.ThrowableAnalyzer;

import java.io.IOException;

/**
 * 授权服务器
 * @author 陈建峰
 * @since 2022/4/30 11:03 下午
 */
@SuppressWarnings("deprecation")
@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final ICurrentUserService currentUserService;
    private final ICurrentClientService currentClientService;
    private final AuthenticationManager authenticationManager;
    private final TokenEnhancerChain tokenEnhancerChain;
    // private final DataSource dataSource;
    private final TokenStore tokenStore;
    private final ITokenGranterProvider tokenGranterProvider;

//    @Bean
//    public ClientDetailsService jdbcClientDetailsService(){
//        return new JdbcClientDetailsService(dataSource);
//    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(
                new ClientDetailsServiceImpl(currentClientService));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.OPTIONS)
                .tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .tokenGranter(tokenGranterProvider.getCompositeTokenGranter(endpoints,
                        currentUserService, authenticationManager))
                .exceptionTranslator(new WebResponseExceptionTranslator());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer
                .allowFormAuthenticationForClients()
                .passwordEncoder(currentClientService.getAuthorizationPasswordEncoder())
                .tokenKeyAccess("isAuthenticated()")
                .checkTokenAccess("permitAll()");
    }

    /**
     * 自定义实现授权模式配置
     */
    @AllArgsConstructor
    static class ClientDetailsServiceImpl implements ClientDetailsService {

        private final ICurrentClientService currentClientService;

        @Override
        public ClientDetails loadClientByClientId(String s) throws ClientRegistrationException {
            return currentClientService.getAuthorizationClientById(s);
        }
    }


    @Slf4j
    static class WebResponseExceptionTranslator
            implements org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator<OAuth2Exception> {

        private final ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

        @Override
        public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
            // InvalidGrantException
            Exception ase = (InvalidGrantException)throwableAnalyzer
                    .getFirstThrowableOfType(InvalidGrantException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new InvalidGrantException(ResultFail.AUTH_INVALID_GRANT.getMessage()));
            }

            // InvalidClientException
            ase = (InvalidClientException)throwableAnalyzer
                    .getFirstThrowableOfType(InvalidClientException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new InvalidClientException(ResultFail.AUTH_INVALID_CLIENT.getMessage()));
            }

            // InvalidScopeException
            ase = (InvalidScopeException)throwableAnalyzer
                    .getFirstThrowableOfType(InvalidScopeException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new InvalidScopeException(ResultFail.AUTH_INVALID_SCOPE.getMessage()));
            }

            // InvalidRequestException
            ase = (InvalidRequestException)throwableAnalyzer
                    .getFirstThrowableOfType(InvalidRequestException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new InvalidRequestException(ResultFail.AUTH_INVALID_REQUEST.getMessage()));
            }

            // UnsupportedGrantTypeException
            ase = (UnsupportedGrantTypeException)throwableAnalyzer
                    .getFirstThrowableOfType(UnsupportedGrantTypeException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new UnsupportedGrantTypeException(ResultFail.AUTH_INVALID_GRANT.getMessage()));
            }

            // UsernameOrPasswordErrorException
            ase = (UsernameOrPasswordErrorException)throwableAnalyzer
                    .getFirstThrowableOfType(UsernameOrPasswordErrorException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new UsernameOrPasswordErrorException(ResultFail.AUTH_USERNAME_OR_PASSWORD_ERROR.getMessage()));
            }

            // PasswdAuthenticationException
            ase = (PasswdAuthenticationException)throwableAnalyzer
                    .getFirstThrowableOfType(PasswdAuthenticationException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new PasswdAuthenticationException(ResultFail.AUTH_USERNAME_OR_PASSWORD_ERROR.getMessage()));
            }

            // CaptchaAuthenticationException
            ase = (CaptchaAuthenticationException)throwableAnalyzer
                    .getFirstThrowableOfType(CaptchaAuthenticationException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new CaptchaAuthenticationException(ResultFail.AUTH_INVALID_CAPTCHA.getMessage()));
            }

            // AccessTokenAuthenticationException
            ase = (AccessTokenAuthenticationException)throwableAnalyzer
                    .getFirstThrowableOfType(AccessTokenAuthenticationException.class, causeChain);
            if(ObjectUtil.isNotNull(ase)) {
                log.error(ase.getMessage());
                return handleOauth2Exception(new AccessTokenAuthenticationException(ResultFail.AUTH_INVALID_TOKEN.getMessage()));
            }

            return handleOauth2Exception(
                    new InvalidUnknownException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));
        }

        private ResponseEntity<OAuth2Exception> handleOauth2Exception(OAuth2Exception e) throws IOException {
            HttpHeaders headers = new HttpHeaders();
            headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
            headers.set(HttpHeaders.PRAGMA, "no-cache");
            return new ResponseEntity<>(new Oauth2Exception(e.getMessage(), e.getHttpErrorCode()), headers,
                    HttpStatus.UNAUTHORIZED);
        }
    }



}
